﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using zj.Communicationlib.ByteArrays;

namespace zj.Communicationlib.Modbus
{
    public class ModbusTCP
    {
        private Socket socket;
        #region 属性
        /// <summary>
        /// TCP连接的状态
        /// </summary>
        public bool IsConnected { get; set; } = false;

        public int ReceiveTimeout
        {
            get { return socket.ReceiveTimeout; }
            set { socket.ReceiveTimeout = value; }
        }

        public int SendTimeout
        {
            get { return socket.SendTimeout; }
            set { socket.SendTimeout = value; }
        }
        public int SleepTime { get; set; } = 10;
        /// <summary>
        /// 收不到数据时，最大的时间长度 毫秒单位
        /// </summary>
        public int TimeOut { get; set; } = 5000;
        /// <summary>
        /// 单元标识符（从站地址），一般不用，
        /// 万一某一个设备在一个TCP连接中用到多个站地址，可以使用此地址
        /// </summary>
        public byte SlaveId { get; set; } = 0x01;
        #endregion
        /// <summary>
        /// 构造函数，初始化Socket
        /// </summary>
        public ModbusTCP()
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            SendTimeout = 2000;
            ReceiveTimeout = 2000;

        }
        #region  连接和断开TCP通信
        /// <summary>
        /// 建立TCP连接
        /// </summary>
        /// <param name="ip">IP地址或域名（主机名）</param>
        /// <param name="port">端口号</param>
        /// <returns></returns>
        public bool Connect(string ip, int port)
        {
            try
            {
                if (IPAddress.TryParse(ip, out IPAddress ipAddress)) //判断是否时IP地址
                {
                    socket.Connect(ipAddress, port);     //是，用IP地址连接
                }
                else
                {
                    socket.Connect(ip, port);             //否,就用域名连接
                }


            }
            catch
            {
                IsConnected = false;
                return false;

            }
            IsConnected = true;
            return true;
        }
        public void DisConnected()
        {
            if (socket != null)
            {
                socket.Disconnect(true);

                IsConnected = false;
            }
        }
        public void Close()
        {
            if (socket != null)
            {
                socket.Close();
            }
        }
        #endregion
        #region Modbus通信
        /// <summary>
        /// 读取多线圈数据
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="modbusFunction"></param>
        /// <param name="startAddr"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="Exception"></exception>
        public bool[] ReadMultiCoil(byte slaveId, RegisterSaveType saveType, ushort startAddr, ushort length)
        {
            // 事务处理标识符（二个字节）；协议标识符（二个字节）；长度（二个字节）；单元标识符（一个字节）
            // 功能码（一个字节）；开始线圈地址（二个字节）； 读取线圈数量（二个字节）;
            byte functionCode = 0x00; 
            if ((int)saveType > 2 || (int)saveType < 1)
            {
                throw new ArgumentException("ModbusTCP功能输入错误！");
            }
            if (saveType == RegisterSaveType.Input)
            {
                functionCode = 0x02;    //读取输入
            }
            else if (saveType == RegisterSaveType.Hold)
            {
                functionCode = 0x01;     //读取输出
            }
            ByteArray send = new ByteArray();
            send.AddRange(0x00, 0x00);  //事务处理标识符
            send.AddRange(0x00, 0x00);  //协议标识符
            send.AddRange(0x00, 0x06);  //长度
            send.Add(slaveId);
            send.Add(functionCode);
            send.Add(startAddr);
            send.Add(length);
            ByteArray receive = new ByteArray();
            try
            {
                if (SendAndReceive(send, ref receive))
                {
                    int resultLength = length % 8 == 0 ? length / 8 : (length / 8) + 1;    //获取结果的字节长度
                    if (receive.Length == resultLength + 9)      //验证报文总长度
                    {
                        if (ByteArrayHelper.ByteArrayCompare(send, receive, 8) && receive[8] == (byte)resultLength)   //前几个字节相同
                        {
                            byte[] result = new byte[resultLength];
                            Array.Copy(receive.Array, 9, result, 0, resultLength);
                            bool[] bolResult = new bool[length];
                            bolResult = ByteArrayHelper.ByteArrayToBoolArray(result, length);
                            return bolResult;
                        }

                    }

                }
                return null;
            }
            catch (Exception ex)
            {
                throw new Exception($"modbusTCP读取{functionCode}指令错误，请检查。 原因：" + ex.Message);
            }
        }
        /// <summary>
        /// 读取多寄存器数据
        /// </summary>
        /// <param name="slaveId"></param>
        /// <param name="modbusFunction"></param>
        /// <param name="startAddr"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="Exception"></exception>
        public byte[] ReadMultiRegister(byte slaveId, RegisterSaveType saveType, ushort startAddr, ushort length)
        {
            // 事务处理标识符（二个字节）；协议标识符（二个字节）；长度（二个字节）；单元标识符（一个字节）
            // 功能码（一个字节）；开始寄存器地址(二个字节)； 读取寄存器数量二个字节); 
            byte functionCode = 0x00;
            if ((int)saveType > 2 || (int)saveType < 1)
            {
                throw new ArgumentException("ModbusTCP功能输入错误！");
            }
            if (saveType == RegisterSaveType.Input)
            {
                functionCode = 0x04;    //读取输入
            }
            else if (saveType == RegisterSaveType.Hold)
            {
                functionCode = 0x03;     //读取输出
            }
            ByteArray send = new ByteArray();
            send.AddRange(0x00, 0x00);  //事务处理标识符
            send.AddRange(0x00, 0x00);  //协议标识符
            send.AddRange(0x00, 0x06);  //长度
            send.Add(slaveId);
            send.Add(functionCode);
            send.Add(startAddr);
            send.Add(length);
            ByteArray receive = new ByteArray();
            try
            {
                if (SendAndReceive(send, ref receive))
                {
                    int resultLength = length * 2;    //获取结果的字节长度
                    if (receive.Length == resultLength + 9)      //验证报文总长度
                    {
                        if (ByteArrayHelper.ByteArrayCompare(send, receive, 8) && receive[8] == (byte)resultLength)   //前几个字节相同
                        {
                            byte[] result = new byte[resultLength];
                            Array.Copy(receive.Array, 9, result, 0, resultLength);
                            return result;
                        }

                    }

                }
                return null;
            }
            catch (Exception ex)
            {
                throw new Exception($"modbusTCP读取{functionCode}指令错误，请检查。 原因：" + ex.Message);
            }
        }
        public bool WriteSingleCoil(byte slaveId, ushort startAddr, bool value)
        {
            //	事务处理标识符（二个字节）；协议标识符（二个字节）；长度（二个字节）；单元标识符（一个字节）
            //	05功能码（一个字节）；线圈地址（二个字节）； 值（二个字节）; 

            ByteArray send = new ByteArray();
            send.AddRange(0x00, 0x00);
            send.AddRange(0x00, 0x00);
            send.AddRange(0x00, 0x06);
            send.Add(slaveId);
            send.Add(0x05);
            send.Add(startAddr);
            send.Add((byte)(value ? 0xFF : 0x00));
            send.Add(0x00);
            ByteArray receive = new ByteArray();
            try
            {
                SendAndReceive(send, ref receive);
                if (ByteArrayHelper.ByteArrayCompare(send, receive))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                throw new Exception("modbusTCP读取05指令错误，请检查。 原因：" + ex.Message);
            }
        }

        public bool WriteSingleRegister(byte slaveId, ushort startAddr, byte[] value)
        {
            ByteArray send = new ByteArray();
            send.AddRange(0x00, 0x00);
            send.AddRange(0x00, 0x00);
            send.AddRange(0x00, 0x06);
            send.Add(slaveId);
            send.Add(0x06);
            send.Add(startAddr);
            send.AddRange(value);
            ByteArray receive = new ByteArray();
            try
            {
                SendAndReceive(send, ref receive);
                if (ByteArrayHelper.ByteArrayCompare(send,receive))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch(Exception ex) 
            {
                throw new Exception("modbusTCP读取06指令错误，请检查。 原因：" + ex.Message); 
            }
        }
        public bool WriteSingleRegister(byte slaveId, ushort startAddr, short value)
        {
            return WriteSingleRegister(slaveId, startAddr, BitConverter.GetBytes(value).Reverse().ToArray());
        }
        public bool WriteSingleRegister(byte slaveId, ushort startAddr, ushort value)
        {
            return WriteSingleRegister(slaveId, startAddr, BitConverter.GetBytes(value).Reverse().ToArray());
        }

        public bool WriteMultiCoils(byte slaveId, short startAddr, bool[] value)
        {
            int length = value.Length % 8 == 0 ? value.Length / 8 : value.Length / 8 + 1;
            ByteArray send = new ByteArray();
            send.AddRange(0x00, 0x00);
            send.AddRange(0x00, 0x00);
            send.Add((short)(length + 7));
            send.Add(slaveId);
            send.Add(0x0F);
            send.Add(startAddr);
            send.Add((short)value.Length);
            send.Add((byte)length);
            send.Add(ByteArrayHelper.BoolArrayToByteArray(value));
            ByteArray receive = new ByteArray();
            try
            {
                SendAndReceive(send,ref receive);
                if(ByteArrayHelper .ByteArrayCompare (send,receive,12))
                {
                    return true;
                }
                else
                {
                    return false;
                }

            }
            catch(Exception ex)
            {
                throw new Exception("modbusTCP读取0F指令错误，请检查。 原因：" + ex.Message);
            }

        }

        public bool WriteMultiRegister(byte slaveId, short startAddr, byte[] values)
        {
            //	事务处理标识符（二个字节）；协议标识符（二个字节）；长度（二个字节）；
            //	单元标识符（一个字节） 10功能码（一个字节）；起始寄存器地址；寄存器数量 ;
            //	字节计数(一个字节)；写入的数据(每个数据两个字节)；
            int valueLength = values.Length;
            ByteArray send = new ByteArray();
            send.AddRange(0x00, 0x00);
            send.AddRange(0x00, 0x00);
            send.Add( (short)(valueLength + 7));
            send.Add(slaveId);
            send.Add(0x10);
            send.Add(startAddr);
            send.Add((short)(valueLength / 2));
            send.Add((byte)(valueLength));
            send.AddRange(values);
            ByteArray receive = new ByteArray();
            try
            {
                SendAndReceive(send,ref receive);
                if(ByteArrayHelper.ByteArrayCompare(send,receive,12))
                {
                    return true;
                }
                else
                {
                    return false;
                }

            }
            catch (Exception ex)
            {
                throw new Exception("modbusTCP读取10指令错误，请检查。 原因：" + ex.Message);
            }





        }

        #endregion
        #region 通用的发送和接收
        private bool SendAndReceive(ByteArray send, ref ByteArray receive)
        {
            int times = 0;
            try
            {
                Monitor.Enter(this);
                socket.Send(send.Array, send.Length, SocketFlags.None);
                Thread.Sleep(SleepTime);
                byte[] buffer = new byte[1024];
                DateTime StartReceiveTime = DateTime.Now;
                while (true)
                {

                    if (socket.Available > 0)  //有数据
                    {
                        int count = socket.Receive(buffer, buffer.Length, SocketFlags.None);  //读取数据并返回实际数据长度
                        receive.AddRange(buffer, count);                                      //写入信息流
                    }
                    else
                    {
                        times++;
                        if (receive.Length > 0)    //读到了数据
                        {
                            return true;
                        }
                        else if (DateTime.Now.Subtract(StartReceiveTime).Milliseconds > TimeOut)
                        {
                            return false;
                        }
                    }
                }

            }
            catch (Exception ex)
            {
                throw new Exception("TCP读写出错！原因：" + ex.Message);
            }
            finally
            {
                Monitor.Exit(this);
            }




        }
        #endregion

    }
}
